package com.gitee.l0km.codegen.base;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.RepeatableContainers;
import org.springframework.stereotype.Component;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ValueConstants;

import com.gitee.l0km.aocache.annotations.AoWeakCacheable;
import com.gitee.l0km.casban.CasbanScanners;
import com.gitee.l0km.codegen.annotations.DeriveMethod;
import com.gitee.l0km.codegen.annotations.ServicePort;
import com.gitee.l0km.codegen.base.Method.Parameter;
import com.gitee.l0km.com4j.basex.AnnotationProxy;
import com.gitee.l0km.com4j.basex.TypeNameUtils;
import com.gitee.l0km.com4j.basex.TypeNameUtils.FullName;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;

import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.base.Strings.commonPrefix;

public class AnnotationHelper {
	private static class  Singleton{
		
		/**
		 * 要排除的注释类型
		 */
		private static final ImmutableSet<Class<? extends Annotation>> BUILTIN_EXCLUDE_ANNOTATIONS;
		/**
		 * 要排除的注释类的包名
		 */
		private static final ImmutableSet<String> BUILTIN_EXCLUDE_ANNOT_PACKAGES;
		/**
		 * 要排除的注释类的类全名
		 */
		public static final ImmutableSet<String> BUILTIN_EXCLUDE_ANNOT_CLASSENAMES;
		static {
			BUILTIN_EXCLUDE_ANNOTATIONS = ImmutableSet.<Class<? extends Annotation>>builder()
					.add(SuppressWarnings.class)
					.add(DeriveMethod.class)
					.add(ServicePort.class)
					.add(ApiOperation.class)
					.add(ResponseBody.class)
					.add(ApiImplicitParams.class)
					.add(Component.class)
					.add(Configuration.class)
					.add(DependsOn.class)
					.build();
			/** 以'*'结尾代表包含子包名 */
			BUILTIN_EXCLUDE_ANNOT_PACKAGES = ImmutableSet.<String>builder()
					.add("javax.validation.*")
					.add("org.aspectj.lang.annotation.")
					.add("gu.sql2java.annotations.")
					.add("com.gitee.l0km.casban.annotations.")
					.build();
			BUILTIN_EXCLUDE_ANNOT_CLASSENAMES = ImmutableSet.<String>builder()
					.add("com.gitee.l0km.aocache.annotations.AoCacheable")
					.add("com.gitee.l0km.aocache.annotations.AoWeakCacheable")
					.add("com.gitee.l0km.aocache.annotations.AoClear")
					.add("com.gitee.l0km.aocache.annotations.AoClears")
					.build();
		}
	}
	private Set<String> excludeAnnotations = Sets.newHashSet();
	public Set<String> getExcludeAnnotations() {
		return excludeAnnotations;
	}
	public void setExcludeAnnotations(Set<String> excludeAnnotations) {
		if(null != excludeAnnotations && !excludeAnnotations.isEmpty()){
			this.excludeAnnotations = excludeAnnotations;
		}
	}
	private boolean withExcludePackage(Annotation a) {
		String pkg = a.annotationType().getPackage().getName();
		if(Singleton.BUILTIN_EXCLUDE_ANNOT_PACKAGES.contains(pkg)) {
			return true;
		}
		return Singleton.BUILTIN_EXCLUDE_ANNOT_PACKAGES
				.stream().anyMatch(p->p.endsWith("*") && 
						commonPrefix(p, pkg).equals(p.substring(0, p.indexOf('*'))));
	}
	public boolean isExcludeAnnotation(Annotation a){
		return Singleton.BUILTIN_EXCLUDE_ANNOTATIONS.contains(a.annotationType()) 
				|| Singleton.BUILTIN_EXCLUDE_ANNOT_CLASSENAMES.contains(a.annotationType().getName())
				|| withExcludePackage(a)
				|| (null != excludeAnnotations && excludeAnnotations.contains(a.annotationType().getName()));
	}
	public boolean isFieldAnnotation(Annotation a) {
		if(null != a) {
			Target target =a.annotationType().getAnnotation(Target.class);
			if(null == target) {
				return true;
			}
			return Iterables.tryFind(Arrays.asList(target.value()),e->ElementType.FIELD.equals(e)).isPresent();
		}
		return false;
	}
	public AnnotationProxy<? extends Annotation> normalizeAnnotation(Method method,Annotation annot){
		if(null != method && null != annot){
			@SuppressWarnings({ "rawtypes", "unchecked" })
			AnnotationProxy<Annotation> rma = new AnnotationProxy(annot);
			return rma;
		}
		return null;
	}
	public AnnotationProxy<? extends Annotation> normalizeAnnotation(AnnotatedElement element,Annotation annot){
		if(null != element && null != annot){
			@SuppressWarnings({ "rawtypes", "unchecked" })
			AnnotationProxy<Annotation> rma = new AnnotationProxy(annot);
			return rma;
		}
		return null;
	}
	/**
	 * 返回 {@link AnnotatedElement} 上所有不使用casban AliasFor注解的注解[不重复]
	 * @param element
	 * @param searchStrategy 参见 {@link SearchStrategy}
	 */
	private static ImmutableList<MergedAnnotation<Annotation>> allNocasbanMergedAnnoationsOf(
			AnnotatedElement element, String searchStrategy){
		if(null != element){
			MergedAnnotations mergedAnnotations = MergedAnnotations.from(element, 
					SearchStrategy.valueOf(searchStrategy), RepeatableContainers.none());
			/** 所有非没有使用 casban AliasFor 注解的注解类型 */ 
			List<MergedAnnotation<Annotation>> nometa = mergedAnnotations.stream()
				.filter(a->!a.isMetaPresent())
				.filter(a->!CasbanScanners.withCasbanAliasFor(a.getType()))
				.collect(Collectors.toList());
			return ImmutableList.copyOf(nometa);
		}
		return ImmutableList.of();
	}
	/**
	 * 返回{@link AnnotatedElement} 上所有使用casban AliasFor注解的注解[不重复]
	 * @param element
	 * @param searchStrategy 参见 {@link SearchStrategy}
	 */
	private static ImmutableList<com.gitee.l0km.common.spring.core.annotation.MergedAnnotation<Annotation>> 
	allCasbanMergedAnnoationsOf(AnnotatedElement element, String searchStrategy){
		if(null != element){
			com.gitee.l0km.common.spring.core.annotation.MergedAnnotations mergedAnnotations= 
					com.gitee.l0km.common.spring.core.annotation.TypeMappedAnnotations
					.from(element, 
							com.gitee.l0km.common.spring.core.annotation.MergedAnnotations.SearchStrategy.valueOf(searchStrategy), 
							com.gitee.l0km.common.spring.core.annotation.RepeatableContainers.none());
			Set<com.gitee.l0km.common.spring.core.annotation.MergedAnnotation<Annotation>> nometa = StreamSupport.stream(
					Spliterators.spliteratorUnknownSize(mergedAnnotations.iterator(), Spliterator.ORDERED), false)
					.filter(a->!a.isMetaPresent())
					.filter(a->CasbanScanners.withCasbanAliasFor(a.getType()))
					.collect(Collectors.toSet());
			return ImmutableList.copyOf(nometa);
		}
		return ImmutableList.of();
	}
	/**
	 * 返回方法上所有不使用casban AliasFor注解的注解类型
	 * @param method
	 */
	private ImmutableList<AnnotationProxy<?>> allNocasbanAnnoationsOf(Method method){
		if(null != method){
			ImmutableList<MergedAnnotation<Annotation>> mergedAnnotations = allNocasbanMergedAnnoationsOf(method.delegate(), "SUPERCLASS");
			return ImmutableList.copyOf(mergedAnnotations.stream()
					.map(c->c.synthesize())
					.map(a->normalizeAnnotation(method, a))
					.collect(Collectors.toList()));
		}
		return ImmutableList.of();
	}
	/**
	 * 返回方法上所有使用casban AliasFor注解的注解类型
	 * @param method
	 * @param searchStrategy 参见 {@link SearchStrategy}
	 */
	private ImmutableList<AnnotationProxy<?>> allCasbanAnnoationsOf(Method method, String searchStrategy){
		if(null != method){
			ImmutableList<com.gitee.l0km.common.spring.core.annotation.MergedAnnotation<Annotation>> mergedAnnotations = 
					allCasbanMergedAnnoationsOf(method.delegate(), searchStrategy);
			return ImmutableList.copyOf(mergedAnnotations.stream()
					.map(c->c.synthesize())
					.map(a->normalizeAnnotation(method, a))
					.collect(Collectors.toList()));
		}
		return ImmutableList.of();
	}
	/**
	 * 返回方法上所有注解(不重复)包含父类注解
	 * @param element
	 * @param searchStrategy 参见 {@link SearchStrategy}
	 */
	@AoWeakCacheable
	private ImmutableList<AnnotationProxy<?>> allAnnoationsOf(AnnotatedElement element, String searchStrategy){
		if(null != element){
			List<AnnotationProxy<?>> annotationProxys = 
					allNocasbanMergedAnnoationsOf(element, searchStrategy).stream()
					.map(c->c.synthesize())
					.map(a->normalizeAnnotation(element, a))
					.collect(Collectors.toList());
			List<AnnotationProxy<?>> casbanAnnotationProxys = 
					allCasbanMergedAnnoationsOf(element, searchStrategy).stream()
					.map(c->c.synthesize())
					.map(a->normalizeAnnotation(element, a))
					.collect(Collectors.toList());
			return FluentIterable.from(annotationProxys)
					.append(casbanAnnotationProxys)
					.toSortedList((o1,o2)->o1.annotationType().getName().compareTo(o2.annotationType().getName()));
		}
		return ImmutableList.of();
	}
	/**
	 * 返回方法上所有注解(不重复)包含父类注解
	 * @param method
	 * @param searchStrategy 参见 {@link SearchStrategy}
	 */
	@AoWeakCacheable
	private ImmutableList<AnnotationProxy<?>> allAnnoationsOf(Method method, String searchStrategy){
		if(null != method){
			ImmutableList<AnnotationProxy<?>> annotations = allNocasbanAnnoationsOf(method);
			return FluentIterable.from(annotations)
					.append(allCasbanAnnoationsOf(method, searchStrategy))
					.toList();
		}
		return ImmutableList.of();
	}
	/**
	 * @since 2.3.0
	 */
	public List<AnnotationProxy<?>> annotationOf(AnnotatedElement element, String searchStrategy){
		return allAnnoationsOf(element, searchStrategy).stream()
				.filter(a->!isExcludeAnnotation(a))
				.sorted((l,r)->l.annotationType().getName().compareTo(r.annotationType().getName()))
				.collect(Collectors.toList());
	}
	public List<AnnotationProxy<?>> annotationOf(Method method, String searchStrategy){
		return allAnnoationsOf(method, searchStrategy).stream()
				.filter(a->!isExcludeAnnotation(a))
				.sorted((l,r)->l.annotationType().getName().compareTo(r.annotationType().getName()))
				.collect(Collectors.toList());
	}
	public List<AnnotationProxy<?>> annotationOf(Parameter parameter){
		if(null != parameter){
			return parameter.getAnnotations().stream().map(a->new AnnotationProxy<>(a))
				.filter(a->!isExcludeAnnotation(a)&& isFieldAnnotation(a))
				.sorted((l,r)->l.annotationType().getName().compareTo(r.annotationType().getName()))
				.collect(Collectors.toList());
		}
		return ImmutableList.of();
	}
	private static Iterable<String>asClassNames(Iterable<AnnotationProxy<?>> input,FullName fn){
		return FluentIterable.from(input).transform(ap->ap.toString(c->TypeNameUtils.getTypeName((Type)c, fn)));
	}
	public String annoationCodeOf(Parameter parameter,String delim, FullName fn){
		return Joiner.on(MoreObjects.firstNonNull(delim, " ")).join(asClassNames(annotationOf(parameter),fn));
	}
	public String annoationCodeOf(Method method,String delim, String searchStrategy, FullName fn){
		return Joiner.on(MoreObjects.firstNonNull(delim, "\n    ")).join(asClassNames(annotationOf(method, searchStrategy),fn));
	}
	/**
	 * @param fn 
	 * @since 2.3.0
	 */
	public String annoationCodeOf(AnnotatedElement element,String delim, String searchStrategy, FullName fn){
		return Joiner.on(MoreObjects.firstNonNull(delim, "\n    ")).join(asClassNames(annotationOf(element, searchStrategy),fn));
	}
	/**
	 * 将方法中的注释引用的类添加到import class中
	 * @param element
	 * @param searchStrategy 参见 {@link SearchStrategy}
	 */
	public LinkedHashSet<Type> annotationClassOf(AnnotatedElement element, String searchStrategy) {
		
		// 将方法中的注释引用的类添加到import class中
		LinkedHashSet<Type> classes = Sets.newLinkedHashSet();
		List<AnnotationProxy<?>> annots = Lists.newLinkedList(allAnnoationsOf(element, searchStrategy));
		Iterable<AnnotationProxy<?>> filtered = Iterables.filter(annots, 
				a->!isExcludeAnnotation(a));
		classes.addAll(Sets.newHashSet(Iterables.transform(filtered, p->p.annotationType())));
		filtered.forEach(p->classes.addAll(p.getImportedClasses()));
		return classes;
	}
	/**
	 * 将方法中的注释引用的类添加到import class中
	 * @param method
	 * @param includeParameter 是否包括参数注解
	 * @param searchStrategy 参见 {@link SearchStrategy}
	 */
	public LinkedHashSet<Type> annotationClassOf(Method method, boolean includeParameter, String searchStrategy) {
		
		// 将方法中的注释引用的类添加到import class中
		LinkedHashSet<Type> classes = Sets.newLinkedHashSet();
		List<AnnotationProxy<?>> annots = Lists.newLinkedList(allAnnoationsOf(method, searchStrategy));
		if(includeParameter) {
			for(Annotation[] z1:method.getParameterAnnotations()){
				annots.addAll(Lists.transform(Arrays.asList(z1), a->new AnnotationProxy<>(a)));
			}
		}
		Iterable<AnnotationProxy<?>> filtered = Iterables.filter(annots, 
				a->!isExcludeAnnotation(a));
		classes.addAll(Sets.newHashSet(Iterables.transform(filtered, p->p.annotationType())));
		filtered.forEach(p->classes.addAll(p.getImportedClasses()));
		return classes;
	}
	/**
	 * 判断方法上是否有 {@code annotationType} 指定的注解
	 * @since 2.3.0
	 */
	public boolean withAnnotation(Method method,String annotationType, String searchStrategy) {
		return annotationOf(method,searchStrategy)
				.stream()
				.anyMatch(p->p.annotationType().getName().equals(annotationType));
	}
	/**
	 * 判断 {@link AnnotatedElement} 上是否有 {@code annotationType} 指定的注解
	 * @since 2.3.0
	 */
	public boolean withAnnotation(AnnotatedElement element,String annotationType, String searchStrategy) {
		return annotationOf(element,searchStrategy)
				.stream()
				.anyMatch(p->p.annotationType().getName().equals(annotationType));
	}
	/**
	 * @since 2.3.0
	 */
	public boolean withAnnotation(Method method,Class<? extends Annotation>annotationType, String searchStrategy) {
		return null == annotationType? false: withAnnotation(method,annotationType.getName(),searchStrategy);
	}
	/**
	 * @since 2.3.0
	 */
	public boolean withAnnotation(AnnotatedElement element,Class<? extends Annotation>annotationType, String searchStrategy) {
		return null == annotationType? false: withAnnotation(element,annotationType.getName(),searchStrategy);
	}
	/**
	 * 返回指定方法的{@link RequestMapping}注释中的method字段，如果为{@code null}则返回默认值{@code defaultValue}
	 * @param method
	 * @param defaultValue 默认值
	 * @return {@code method}为{@code null}则返回{@code defaultValue}
	 */
	public String requestMethodOf(Method method, String defaultValue){
		defaultValue = Strings.isNullOrEmpty(defaultValue)?"POST" : defaultValue;
		RequestMapping rma = AnnotatedElementUtils.findMergedAnnotation(method.delegate(), RequestMapping.class);
		if(null != method && null != rma){
			RequestMapping annot = new AnnotationProxy<RequestMapping>(rma).createAnnotation();
			RequestMethod[] value = annot.method();
			return (null != value && value.length>0)? value[0].name() : defaultValue;
			
		}
		return defaultValue;
	}
	/**
	 * 返回指定方法的{@link RequestMapping}注释中的produces字段，如果为{@code null}则返回空字符串
	 * @param method
	 * @return {@code method}为{@code null}则返回空字符串
	 */
	public String producesOf(Method method){
		RequestMapping rma;
		if(null != method && null != (rma = method.getAnnotation(RequestMapping.class))){
			String[] produces = rma.produces();
			return Joiner.on(',').join(Iterables.filter(Arrays.asList(produces),s->!Strings.isNullOrEmpty(s)));
		}
		return "";
	}
	public String appendProducesIfNoempty(Method method){
		String produces = producesOf(method);
		if(!produces.isEmpty()){
			return String.format(",produces=\"%s\"", produces);
		}
		return "";
	}
	private static ImmutableSet<String> STREAM_CONTANT_TYPES=ImmutableSet.of("application/octet-stream","application/pdf");
	public boolean needResponse(Method method){
		String produces = producesOf(method);
		return produces.isEmpty() ? true : !Iterables.tryFind(STREAM_CONTANT_TYPES, s->produces.indexOf(s)>=0).isPresent();
	}
	public String defaultValueOf(Parameter parameter){
		RequestParam rpa;
		if(null != parameter && null != (rpa = parameter.getAnnotation(RequestParam.class))){
			RequestParam annot = new AnnotationProxy<RequestParam>(rpa).createAnnotation();
			String value = annot.defaultValue();
			return !ValueConstants.DEFAULT_NONE.equals(value) ? value : null; 
			
		}
		return null;
	}
	public String defaultValueCodeOf(Parameter parameter,String left){
		left = nullToEmpty(left);
		String defaultValue = defaultValueOf(parameter);
		if(null != defaultValue){
			return left + "defaultValue=\"" + defaultValue + "\"";
		}
		return null;
	}
}
